#!/bin/sh
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

#
# pxf-service	start/stop/initialize/status the PXF instance
#

if [ -z ${PXF_HOME} ]; then
    parent_script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
    env_script=${parent_script_dir}/conf/pxf-env.sh
else
    env_script=${PXF_HOME}/conf/pxf-env.sh
fi

# load pxf-env.sh script
if [ ! -f ${env_script} ]; then
	echo WARNING: failed to find $env_script
else
	source ${env_script}
fi

pxf_user=${PXF_USER}
instance_port=${PXF_PORT:-@pxfPortNum@}
instance_name=pxf-service

if [ -z ${PXF_HOME} ]; then
    # RPM based setup
    pxf_root=/usr/lib/pxf
    tomcat_root=/opt/apache-tomcat
    tomcat_templates=/opt/pxf/tomcat-templates
    instance_root=/var/pxf
    instance_owner=$pxf_user:$pxf_user
else
    # OSS/Source code based setup
    pxf_root=${PXF_HOME}/lib
    tomcat_root=${PXF_HOME}/apache-tomcat
    tomcat_templates=${PXF_HOME}/tomcat-templates
    instance_root=${PXF_HOME}
    instance_owner=${pxf_user}
fi

curl=`which curl`

# print error message and return with error code
function fail()
{
    echo "ERROR: $1"
    exit 1
}

#
# createInstance creates a tomcat instance and
# configures based on pre-configured template files.
#
function createInstance()
{
	mkdir -p ${instance_root}
	mkdir -p ${instance_root}/${instance_name}
	cp -r ${tomcat_root}/* ${instance_root}/${instance_name}/.

	if [ $? -gt 0 ]; then
		echo ERROR: instance creation failed
		return 1
	fi

    if [ ! -z ${pxf_user} ]; then
        chown -R ${instance_owner} ${instance_root}
    fi
    chmod 700 ${instance_root}/${instance_name}

    # copy configuration files into instance
    cp ${tomcat_templates}/bin/setenv.sh ${instance_root}/${instance_name}/bin/setenv.sh
    cp ${tomcat_templates}/conf/catalina.properties ${instance_root}/${instance_name}/conf/.
    cp ${tomcat_templates}/conf/server.xml ${instance_root}/${instance_name}/conf/.
    cp ${tomcat_templates}/conf/web.xml ${instance_root}/${instance_name}/conf/.
    return 0
}

#
# deployWebapp adds the pxf-webapp to the new instance's webapps folder
# and the custom loader to the instance's lib directory
#
function deployWebapp()
{
	cp ${pxf_root}/pxf.war ${instance_root}/${instance_name}/webapps/ || return 1
	cp ${pxf_root}/pxf-service-*[0-9].jar ${instance_root}/${instance_name}/lib/ || return 1

	return 0
}

#
# waitForTomcat waits for tomcat to finish loading
# for given attempts number.
#
function waitForTomcat()
{
	attempts=0
	max_attempts=$1 # number of attempts to connect
	sleep_time=1 # sleep 1 second between attempts

	# wait until tomcat is up:
	echo Checking if tomcat is up and running...
	until [[ "`curl --silent --connect-timeout 1 -I http://localhost:${instance_port} | grep 'Coyote'`" != "" ]];
	do
		let attempts=attempts+1
		if [[ "$attempts" -eq "$max_attempts" ]]; then
			echo ERROR: PXF is down - tomcat is not running
			return 1
		fi
		echo "tomcat not responding, re-trying after ${sleep_time} second (attempt number ${attempts})"
		sleep ${sleep_time}
	done

	return 0
}

#
# checkWebapp checks if tomcat is up for $1 attempts and then
# verifies PXF webapp is functional
#
function checkWebapp()
{
	waitForTomcat $1 || return 1

	echo Checking if PXF webapp is up and running...
	curlResponse=$(${curl} -s "http://localhost:${instance_port}/pxf/v0")
	expectedResponse="Wrong version v0, supported version is v[0-9]+"

	if [[ ${curlResponse} =~ $expectedResponse ]]; then
		echo PXF webapp is listening on port ${instance_port}
		return 0
	fi

	echo ERROR: PXF webapp is inaccessible but tomcat is up. Check logs for more information
	return 1
}

# instanceExists returns 0 when the instance exists
# non zero otherwise
function instanceExists()
{
	if [ ! -d "$instance_root/$instance_name" ]; then
		return 1
	fi

	${instance_root}/${instance_name}/bin/catalina.sh version > /dev/null 2>&1
	return $?
}

# doInit handles the init command
function doInit()
{
	instanceExists
	if [ $? -eq 0 ]; then
		echo WARNING: instance already exists in ${instance_root}, nothing to do...
		return 0
	fi
	determineHadoopDistro
	generatePrivateClasspath || return 1
	createInstance || return 1
	deployWebapp || return 1
	createLogsDir || return 1
	createRunDir  || return 1
}

#
# configureWebapp patches the webapp with pxf and user overriden configs
# applied only if PXF_HOME is defined
#
function configureWebapp()
{
    if [ -z ${PXF_HOME} ]; then
        # webapp doesn't require patch
        return 0
    fi
    pushd ${instance_root}/${instance_name}/webapps > /dev/null || return 1
    rm -rf pxf
    mkdir pxf
    cd pxf
    unzip -q ../pxf.war
    popd > /dev/null

    context_file=${instance_root}/${instance_name}/webapps/pxf/META-INF/context.xml
    sed -i -e "s:classpathFiles=\"[a-zA-Z0-9\/\;.-]*\":classpathFiles=\"${PXF_HOME}\/conf\/pxf-private.classpath\":" ${context_file}
    sed -i -e "s:secondaryClasspathFiles=\"[a-zA-Z0-9\/\;.-]*\":secondaryClasspathFiles=\"${PXF_HOME}\/conf\/pxf-public.classpath\":" ${context_file}

    web_file=${instance_root}/${instance_name}/webapps/pxf/WEB-INF/web.xml
    sed -i -e "s:<param-value>.*pxf-log4j.properties<\/param-value>:<param-value>$PXF_HOME\/conf\/pxf-log4j.properties<\/param-value>:" ${web_file}

    # set port
    catalinaProperties=${instance_root}/${instance_name}/conf/catalina.properties
    sed -i -e "s|^[[:blank:]]*connector.http.port=.*$|connector.http.port=$instance_port|g" ${catalinaProperties}

    # set container configs
    catalinaEnv=${instance_root}/${instance_name}/bin/setenv.sh
    sed -i -e "s|JVM_OPTS=.*$|JVM_OPTS=\"${PXF_JVM_OPTS}\"|g" ${catalinaEnv}
    sed -i -e "s|-Dpxf.log.dir=[^[:space:]^\"]*|-Dpxf.log.dir=${PXF_LOGDIR} |g" ${catalinaEnv}
    sed -i -e "s|-Dpxf.service.kerberos.keytab=[^[:space:]^\"]*|-Dpxf.service.kerberos.keytab=${PXF_KEYTAB} |g" ${catalinaEnv}
    sed -i -e "s|-Dpxf.service.kerberos.principal=[^[:space:]^\"]*|-Dpxf.service.kerberos.principal=${PXF_PRINCIPAL} |g" ${catalinaEnv}
    sed -i -e "s|^[[:blank:]]*PXF_USER_IMPERSONATION=.*$|PXF_USER_IMPERSONATION=\"${PXF_USER_IMPERSONATION}\"|g" ${catalinaEnv}
    sed -i -e "s|^[[:blank:]]*CATALINA_PID=.*$|CATALINA_PID=${PXF_RUNDIR}/catalina.pid|g" ${catalinaEnv}
    sed -i -e "s|^[[:blank:]]*CATALINA_OUT=.*$|CATALINA_OUT=${PXF_LOGDIR}/catalina.out|g" ${catalinaEnv}

    # set log directories
    catalinaLog=${instance_root}/$instance_name/conf/logging.properties
    sed -i -e "s|juli.FileHandler.directory\s*=.*$|juli.FileHandler.directory = ${PXF_LOGDIR}|g" ${catalinaLog}
}

function commandWebapp()
{
    command=$1
    pushd ${instance_root} > /dev/null
    if [ ! -z ${pxf_user} ]; then
        # Run command as a pxf_user
        su ${pxf_user} -c "$instance_root/$instance_name/bin/catalina.sh $command"
    else
        # Run command as a current user
        ${instance_root}/${instance_name}/bin/catalina.sh ${command}
    fi
    if [ $? -ne 0 ]; then
        return 1
    fi
    popd > /dev/null
}

function createLogsDir()
{
    mkdir -p ${PXF_LOGDIR}
    if [ ! -z ${pxf_user} ]; then
        chown -R ${instance_owner} ${PXF_LOGDIR}
    fi
    chmod 700 ${PXF_LOGDIR}
    return 0
}

function createRunDir()
{
    mkdir -p ${PXF_RUNDIR}
    if [ ! -z ${pxf_user} ]; then
        chown -R ${instance_owner} ${PXF_RUNDIR}
    fi
    chmod 700 ${PXF_RUNDIR}
    return 0
}

function check_hadoop_install()
{
    local distro_type=${1}
    case "${distro_type}" in
        hdp|HDP)
            if [ -d "/usr/hdp/current/hadoop-client/client" ]; then
                DISTRO="hdp"
                return 0;
            fi
            ;;
        cdh|CDH)
            if [ -d "/usr/lib/hadoop/client" ]; then
                DISTRO="cdh"
                return 0;
            fi
            ;;
        tar|TAR)
            if [ -n "${HADOOP_ROOT}" ] && [ -d "${HADOOP_ROOT}/hadoop/share/hadoop/common/lib" ]; then
                DISTRO="tar"
                return 0;
            fi
            ;;
        custom|CUSTOM)
            # use tarball template for custom distro, do not require HADOOP_ROOT to be set
            DISTRO="tar"
            return 0;
            ;;
        *)
            fail "Unknown hadoop distribution type: HADOOP_DISTRO=${distro_type}"
            ;;
    esac
    # the distro type was not found installed, return failure code
    return 1
}

function determineHadoopDistro()
{
    DISTRO=""
    # check if the distro is explicitly specified
    if [ -z "${HADOOP_DISTRO}" ]; then
        # if distro is not specified, try checking for HDP and then CDH
        check_hadoop_install "hdp" || check_hadoop_install "cdh"
    else
        # check distro specified in the config file
        check_hadoop_install "${HADOOP_DISTRO}"
    fi

    # if still not determined, check tarball-based install
    if [ -z "${DISTRO}" ]; then
        check_hadoop_install "tar"
        # if still unknown, then error out
        if [ -z "${DISTRO}" ]; then
            fail "Can not determine Hadoop distribution, please install Hadoop clients."
        fi
    fi
}

function generatePrivateClasspath()
{
    # distro must have been determined by now, but double check
    if [ -z "${DISTRO}" ]; then
        fail "Hadoop distribution was not determined"
    fi

    # verify that a template file for the distribution exists
    local template_file="${PXF_HOME}/conf-templates/pxf-private-${DISTRO}.classpath.template"
    if [ ! -f "${template_file}" ]; then
        fail "Template file ${template_file} not found"
    fi

    echo "Generating ${PXF_HOME}/conf/pxf-private.classpath file from ${template_file} ..."

    # create initial version of the file by replacing PXF_HOME token (applicable to all templates)
    sed -e "s|PXF_HOME|${PXF_HOME}|g" ${template_file} > ${PXF_HOME}/conf/pxf-private.classpath

    # substitute HADOOP_ROOT value if defined
    if [ -n "${HADOOP_ROOT}" ]; then
        sed -i -e "s|HADOOP_ROOT|${HADOOP_ROOT}|g" ${PXF_HOME}/conf/pxf-private.classpath
    fi
}

function printUsage()
{
    echo $"Usage: $0 {start|stop|restart|init|status}"
}

function validateParameters()
{

    # validate curl
    which curl &> /dev/null
    if [ "$?" -ne "0" ]
    then
        echo "ERROR: curl is not installed, please install"
        exit 1
    fi

    # validate unzip
    which unzip &> /dev/null
    if [ "$?" -ne "0" ]
    then
        echo "ERROR: unzip is not installed, please install"
        exit 1
    fi

    # validate pxf user
    if [ ! -z ${pxf_user} ]; then
        id ${pxf_user}  &> /dev/null;
        if [ "$?" -ne "0" ]
        then
            echo "ERROR: User $pxf_user doesn't exist. Please set valid user via \$PXF_USER variable"
            exit 1
        fi
    fi

    # make sure current user is not root
    if [ $EUID -eq 0 ]; then
        echo "ERROR: Cannot run as root user"
        exit 1
    fi

    # validate JAVA_HOME
    if [ ! -x ${JAVA_HOME}/bin/java ]; then
        echo ERROR: \$JAVA_HOME is invalid
        exit 1
    fi

    return 0
}

#
# doStart handles start command
# command is executed as the user $pxf_user
#
# after start, uses checkWebapp to verify the PXF webapp was loaded
# successfully
#
function doStart()
{
	instanceExists
	if [ $? -ne 0 ]; then
		echo ERROR: cant find PXF instance, maybe call init?
		return 1
	fi
	configureWebapp || return 1
	commandWebapp start || return 1
	checkWebapp 300 || return 1
}

#
# doStart handles stop command
# command is executed as the user $pxf_user
#
#
function doStop()
{
    instanceExists
    if [ $? -ne 0 ]; then
        echo "ERROR: can't find PXF instance, maybe call init?"
        return 1
    fi
    commandWebapp stop || return 1
}

function doStatus()
{
	checkWebapp 1 || return 1
}

command=$1

case "$command" in
	"init" )
		validateParameters
		doInit
		;;
	"start" )
		validateParameters
		doStart
		;;
	"stop" )
		validateParameters
		doStop
		;;
	"restart" )
		validateParameters
		doStop
		sleep 1s
		doStart
		;;
	"status" )
		validateParameters
		doStatus
		;;
	* )
		printUsage
		exit 2
		;;
esac

exit $?
